// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package readerresult

import (
	F "github.com/IBM/fp-go/v2/function"
	L "github.com/IBM/fp-go/v2/optics/lens"
	G "github.com/IBM/fp-go/v2/readereither/generic"
)

// Do creates an empty context of type [S] to be used with the [Bind] operation.
// This is the starting point for do-notation style composition.
//
// Example:
//
//	type State struct {
//	    UserID   string
//	    TenantID string
//	}
//	result := readereither.Do(State{})
func Do[S any](
	empty S,
) ReaderResult[S] {
	return G.Do[ReaderResult[S]](empty)
}

// Bind attaches the result of a computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps
// and access the context.Context from the environment.
//
// The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2.
//
// Example:
//
//	type State struct {
//	    UserID   string
//	    TenantID string
//	}
//
//	result := F.Pipe2(
//	    readereither.Do(State{}),
//	    readereither.Bind(
//	        func(uid string) func(State) State {
//	            return func(s State) State { s.UserID = uid; return s }
//	        },
//	        func(s State) readereither.ReaderResult[string] {
//	            return func(ctx context.Context) either.Either[error, string] {
//	                if uid, ok := ctx.Value("userID").(string); ok {
//	                    return either.Right[error](uid)
//	                }
//	                return either.Left[string](errors.New("no userID"))
//	            }
//	        },
//	    ),
//	    readereither.Bind(
//	        func(tid string) func(State) State {
//	            return func(s State) State { s.TenantID = tid; return s }
//	        },
//	        func(s State) readereither.ReaderResult[string] {
//	            // This can access s.UserID from the previous step
//	            return func(ctx context.Context) either.Either[error, string] {
//	                return either.Right[error]("tenant-" + s.UserID)
//	            }
//	        },
//	    ),
//	)
func Bind[S1, S2, T any](
	setter func(T) func(S1) S2,
	f Kleisli[S1, T],
) Kleisli[ReaderResult[S1], S2] {
	return G.Bind[ReaderResult[S1], ReaderResult[S2]](setter, f)
}

// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[S1, S2, T any](
	setter func(T) func(S1) S2,
	f func(S1) T,
) Kleisli[ReaderResult[S1], S2] {
	return G.Let[ReaderResult[S1], ReaderResult[S2]](setter, f)
}

// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[S1, S2, T any](
	setter func(T) func(S1) S2,
	b T,
) Kleisli[ReaderResult[S1], S2] {
	return G.LetTo[ReaderResult[S1], ReaderResult[S2]](setter, b)
}

// BindTo initializes a new state [S1] from a value [T]
func BindTo[S1, T any](
	setter func(T) S1,
) Kleisli[ReaderResult[T], S1] {
	return G.BindTo[ReaderResult[S1], ReaderResult[T]](setter)
}

// ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other.
//
// Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel.
//
// Example:
//
//	type State struct {
//	    UserID   string
//	    TenantID string
//	}
//
//	// These operations are independent and can be combined with ApS
//	getUserID := func(ctx context.Context) either.Either[error, string] {
//	    return either.Right[error](ctx.Value("userID").(string))
//	}
//	getTenantID := func(ctx context.Context) either.Either[error, string] {
//	    return either.Right[error](ctx.Value("tenantID").(string))
//	}
//
//	result := F.Pipe2(
//	    readereither.Do(State{}),
//	    readereither.ApS(
//	        func(uid string) func(State) State {
//	            return func(s State) State { s.UserID = uid; return s }
//	        },
//	        getUserID,
//	    ),
//	    readereither.ApS(
//	        func(tid string) func(State) State {
//	            return func(s State) State { s.TenantID = tid; return s }
//	        },
//	        getTenantID,
//	    ),
//	)
func ApS[S1, S2, T any](
	setter func(T) func(S1) S2,
	fa ReaderResult[T],
) Kleisli[ReaderResult[S1], S2] {
	return G.ApS[ReaderResult[S1], ReaderResult[S2]](setter, fa)
}

// ApSL is a variant of ApS that uses a lens to focus on a specific field in the state.
// Instead of providing a setter function, you provide a lens that knows how to get and set
// the field. This is more convenient when working with nested structures.
//
// Parameters:
//   - lens: A lens that focuses on a field of type T within state S
//   - fa: A ReaderResult computation that produces a value of type T
//
// Returns:
//   - A function that transforms ReaderResult[S] to ReaderResult[S] by setting the focused field
//
// Example:
//
//	type Person struct {
//	    Name string
//	    Age  int
//	}
//
//	ageLens := lens.MakeLens(
//	    func(p Person) int { return p.Age },
//	    func(p Person, a int) Person { p.Age = a; return p },
//	)
//
//	getAge := func(ctx context.Context) either.Either[error, int] {
//	    return either.Right[error](30)
//	}
//
//	result := F.Pipe1(
//	    readereither.Do(Person{Name: "Alice", Age: 25}),
//	    readereither.ApSL(ageLens, getAge),
//	)
func ApSL[S, T any](
	lens L.Lens[S, T],
	fa ReaderResult[T],
) Kleisli[ReaderResult[S], S] {
	return ApS(lens.Set, fa)
}

// BindL is a variant of Bind that uses a lens to focus on a specific field in the state.
// It combines the lens-based field access with monadic composition, allowing you to:
// 1. Extract a field value using the lens
// 2. Use that value in a computation that may fail
// 3. Update the field with the result
//
// Parameters:
//   - lens: A lens that focuses on a field of type T within state S
//   - f: A function that takes the current field value and returns a ReaderResult computation
//
// Returns:
//   - A function that transforms ReaderResult[S] to ReaderResult[S]
//
// Example:
//
//	type Counter struct {
//	    Value int
//	}
//
//	valueLens := lens.MakeLens(
//	    func(c Counter) int { return c.Value },
//	    func(c Counter, v int) Counter { c.Value = v; return c },
//	)
//
//	increment := func(v int) readereither.ReaderResult[int] {
//	    return func(ctx context.Context) either.Either[error, int] {
//	        if v >= 100 {
//	            return either.Left[int](errors.New("value too large"))
//	        }
//	        return either.Right[error](v + 1)
//	    }
//	}
//
//	result := F.Pipe1(
//	    readereither.Of[error](Counter{Value: 42}),
//	    readereither.BindL(valueLens, increment),
//	)
func BindL[S, T any](
	lens L.Lens[S, T],
	f Kleisli[T, T],
) Kleisli[ReaderResult[S], S] {
	return Bind(lens.Set, F.Flow2(lens.Get, f))
}

// LetL is a variant of Let that uses a lens to focus on a specific field in the state.
// It applies a pure transformation to the focused field without any effects.
//
// Parameters:
//   - lens: A lens that focuses on a field of type T within state S
//   - f: A pure function that transforms the field value
//
// Returns:
//   - A function that transforms ReaderResult[S] to ReaderResult[S]
//
// Example:
//
//	type Counter struct {
//	    Value int
//	}
//
//	valueLens := lens.MakeLens(
//	    func(c Counter) int { return c.Value },
//	    func(c Counter, v int) Counter { c.Value = v; return c },
//	)
//
//	double := func(v int) int { return v * 2 }
//
//	result := F.Pipe1(
//	    readereither.Of[error](Counter{Value: 21}),
//	    readereither.LetL(valueLens, double),
//	)
//	// result when executed will be Right(Counter{Value: 42})
func LetL[S, T any](
	lens L.Lens[S, T],
	f func(T) T,
) Kleisli[ReaderResult[S], S] {
	return Let(lens.Set, F.Flow2(lens.Get, f))
}

// LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state.
// It sets the focused field to a constant value.
//
// Parameters:
//   - lens: A lens that focuses on a field of type T within state S
//   - b: The constant value to set
//
// Returns:
//   - A function that transforms ReaderResult[S] to ReaderResult[S]
//
// Example:
//
//	type Config struct {
//	    Debug   bool
//	    Timeout int
//	}
//
//	debugLens := lens.MakeLens(
//	    func(c Config) bool { return c.Debug },
//	    func(c Config, d bool) Config { c.Debug = d; return c },
//	)
//
//	result := F.Pipe1(
//	    readereither.Of[error](Config{Debug: true, Timeout: 30}),
//	    readereither.LetToL(debugLens, false),
//	)
//	// result when executed will be Right(Config{Debug: false, Timeout: 30})
func LetToL[S, T any](
	lens L.Lens[S, T],
	b T,
) Kleisli[ReaderResult[S], S] {
	return LetTo(lens.Set, b)
}
